require.config({
  paths: {
    bootstrap: "./vendor/bootstrap.min",
    diffMatchPatch: "./vendor/diff_match_patch.min",
    handlebars: "./vendor/handlebars.min",
    handlebarsExtended: "./utils/handlebars_helper",
    jquery: "./vendor/jquery.min",
    locales: "./locales/locale",
    lodash: "./vendor/lodash.custom.min",
    pathToRegexp: "./vendor/path-to-regexp/index",
    prismjs: "./vendor/prism",
    semver: "./vendor/semver.min",
    utilsSampleRequest: "./utils/send_sample_request",
    webfontloader: "./vendor/webfontloader",
    list: "./vendor/list.min",
    apiData: "./api_data",
    apiProject: "./api_project",
  },
  shim: {
    bootstrap: {
      deps: ["jquery"],
    },
    diffMatchPatch: {
      exports: "diff_match_patch",
    },
    handlebars: {
      exports: "Handlebars",
    },
    handlebarsExtended: {
      deps: ["jquery", "handlebars"],
      exports: "Handlebars",
    },
    prismjs: {
      exports: "Prism",
    },
  },
  urlArgs: "v=" + new Date().getTime(),
  waitSeconds: 150,
});

require([
  "jquery",
  "lodash",
  "locales",
  "handlebarsExtended",
  "apiProject",
  "apiData",
  "prismjs",
  "utilsSampleRequest",
  "semver",
  "webfontloader",
  "bootstrap",
  "pathToRegexp",
  "list",
], function (
  $,
  _,
  locale,
  Handlebars,
  apiProject,
  apiData,
  Prism,
  sampleRequest,
  semver,
  WebFont
) {
  // Load google web fonts.
  WebFont.load({
    active: function () {
      // Only init after fonts are loaded.
      init(
        $,
        _,
        locale,
        Handlebars,
        apiProject,
        apiData,
        Prism,
        sampleRequest,
        semver
      );
    },
    inactive: function () {
      // Run init, even if loading fonts fails
      init(
        $,
        _,
        locale,
        Handlebars,
        apiProject,
        apiData,
        Prism,
        sampleRequest,
        semver
      );
    },
    google: {
      families: ["Source Code Pro", "Source Sans Pro:n4,n6,n7"],
    },
  });
});

function init(
  $,
  _,
  locale,
  Handlebars,
  apiProject,
  apiData,
  Prism,
  sampleRequest,
  semver
) {
  var api = apiData.api;

  //
  // Templates
  //
  var templateHeader = Handlebars.compile($("#template-header").html());
  var templateFooter = Handlebars.compile($("#template-footer").html());
  var templateArticle = Handlebars.compile($("#template-article").html());
  var templateCompareArticle = Handlebars.compile(
    $("#template-compare-article").html()
  );
  var templateGenerator = Handlebars.compile($("#template-generator").html());
  var templateProject = Handlebars.compile($("#template-project").html());
  var templateSections = Handlebars.compile($("#template-sections").html());
  var templateSidenav = Handlebars.compile($("#template-sidenav").html());

  //
  // Default host url used if no sampleUrl is present in config
  //
  var baseURL = window.location.origin;

  //
  // apiProject defaults
  //
  if (!apiProject.template) apiProject.template = {};

  if (apiProject.template.withCompare == null)
    apiProject.template.withCompare = true;

  if (apiProject.template.withGenerator == null)
    apiProject.template.withGenerator = true;

  if (apiProject.template.forceLanguage)
    locale.setLanguage(apiProject.template.forceLanguage);

  if (apiProject.template.aloneDisplay == null)
    apiProject.template.aloneDisplay = false;

  // Setup jQuery Ajax
  $.ajaxSetup(apiProject.template.jQueryAjaxSetup);

  //
  // Data transform
  //
  // grouped by group
  var apiByGroup = _.groupBy(api, function (entry) {
    return entry.group;
  });

  // grouped by group and name
  var apiByGroupAndName = {};
  $.each(apiByGroup, function (index, entries) {
    apiByGroupAndName[index] = _.groupBy(entries, function (entry) {
      return entry.name;
    });
  });

  //
  // sort api within a group by title ASC and custom order
  //
  var newList = [];
  var umlauts = { ä: "ae", ü: "ue", ö: "oe", ß: "ss" }; // TODO: remove in version 1.0
  $.each(apiByGroupAndName, function (index, groupEntries) {
    // get titles from the first entry of group[].name[] (name has versioning)
    var titles = [];
    $.each(groupEntries, function (titleName, entries) {
      var title = entries[0].title;
      if (title !== undefined) {
        title.toLowerCase().replace(/[äöüß]/g, function ($0) {
          return umlauts[$0];
        });
        titles.push(title + "#~#" + titleName); // '#~#' keep reference to titleName after sorting
      }
    });
    // sort by name ASC
    titles.sort();

    // custom order
    if (apiProject.order) titles = sortByOrder(titles, apiProject.order, "#~#");

    // add single elements to the new list
    titles.forEach(function (name) {
      var values = name.split("#~#");
      var key = values[1];
      groupEntries[key].forEach(function (entry) {
        newList.push(entry);
      });
    });
  });
  // api overwrite with ordered list
  api = newList;

  //
  // Group- and Versionlists
  //
  var apiGroups = {};
  var apiGroupTitles = {};
  var apiVersions = {};
  apiVersions[apiProject.version] = 1;

  $.each(api, function (index, entry) {
    apiGroups[entry.group] = 1;
    apiGroupTitles[entry.group] = entry.groupTitle || entry.group;
    apiVersions[entry.version] = 1;
  });

  // sort groups
  apiGroups = Object.keys(apiGroups);
  apiGroups.sort();

  // custom order
  if (apiProject.order) apiGroups = sortByOrder(apiGroups, apiProject.order);

  // sort versions DESC
  apiVersions = Object.keys(apiVersions);
  apiVersions.sort(semver.compare);
  apiVersions.reverse();

  //
  // create Navigationlist
  //
  var nav = [];
  apiGroups.forEach(function (group) {
    // Mainmenu entry
    nav.push({
      group: group,
      isHeader: true,
      title: apiGroupTitles[group],
    });

    // Submenu
    var oldName = "";
    api.forEach(function (entry) {
      if (entry.group === group) {
        if (oldName !== entry.name) {
          nav.push({
            title: entry.title,
            group: group,
            name: entry.name,
            type: entry.type,
            version: entry.version,
            url: entry.url,
          });
        } else {
          nav.push({
            title: entry.title,
            group: group,
            hidden: true,
            name: entry.name,
            type: entry.type,
            version: entry.version,
            url: entry.url,
          });
        }
        oldName = entry.name;
      }
    });
  });

  /**
   * Add navigation items by analyzing the HTML content and searching for h1 and h2 tags
   * @param nav Object the navigation array
   * @param content string the compiled HTML content
   * @param index where to insert items
   * @return boolean true if any good-looking (i.e. with a group identifier) <h1> tag was found
   */
  function add_nav(nav, content, index) {
    var found_level1 = false;
    if (!content) {
      return found_level1;
    }
    var topics = content.match(/<h(1|2).*?>(.+?)<\/h(1|2)>/gi);
    if (topics) {
      topics.forEach(function (entry) {
        var level = entry.substring(2, 3);
        var title = entry.replace(/<.+?>/g, ""); // Remove all HTML tags for the title
        var entry_tags = entry.match(/id="api-([^\-]+)(?:-(.+))?"/); // Find the group and name in the id property
        var group = entry_tags ? entry_tags[1] : null;
        var name = entry_tags ? entry_tags[2] : null;
        if (level == 1 && title && group) {
          nav.splice(index, 0, {
            group: group,
            isHeader: true,
            title: title,
            isFixed: true,
          });
          index++;
          found_level1 = true;
        }
        if (level == 2 && title && group && name) {
          nav.splice(index, 0, {
            group: group,
            name: name,
            isHeader: false,
            title: title,
            isFixed: false,
            version: "1.0",
          });
          index++;
        }
      });
    }
    return found_level1;
  }

  // Mainmenu Header entry
  if (apiProject.header) {
    var found_level1 = add_nav(nav, apiProject.header.content, 0); // Add level 1 and 2 titles
    if (!found_level1) {
      // If no Level 1 tags were found, make a title
      nav.unshift({
        group: "_",
        isHeader: true,
        title:
          apiProject.header.title == null
            ? locale.__("General")
            : apiProject.header.title,
        isFixed: true,
      });
    }
  }

  // Mainmenu Footer entry
  if (apiProject.footer) {
    var last_nav_index = nav.length;
    var found_level1 = add_nav(nav, apiProject.footer.content, nav.length); // Add level 1 and 2 titles
    if (!found_level1 && apiProject.footer.title != null) {
      // If no Level 1 tags were found, make a title
      nav.splice(last_nav_index, 0, {
        group: "_footer",
        isHeader: true,
        title: apiProject.footer.title,
        isFixed: true,
      });
    }
  }

  // render pagetitle
  var title = apiProject.title
    ? apiProject.title
    : "apiDoc: " + apiProject.name + " - " + apiProject.version;
  $(document).attr("title", title);

  // remove loader
  $("#loader").remove();

  // render sidenav
  var fields = {
    nav: nav,
  };
  $("#sidenav").append(templateSidenav(fields));

  // render Generator
  $("#generator").append(templateGenerator(apiProject));

  // render Project
  _.extend(apiProject, { versions: apiVersions });
  $("#project").append(templateProject(apiProject));

  // render apiDoc, header/footer documentation
  if (apiProject.header) $("#header").append(templateHeader(apiProject.header));

  if (apiProject.footer) $("#footer").append(templateFooter(apiProject.footer));

  //
  // Render Sections and Articles
  //
  var articleVersions = {};
  var content = "";
  apiGroups.forEach(function (groupEntry) {
    var articles = [];
    var oldName = "";
    var fields = {};
    var title = groupEntry;
    var description = "";
    articleVersions[groupEntry] = {};

    // render all articles of a group
    api.forEach(function (entry) {
      if (groupEntry === entry.group) {
        if (oldName !== entry.name) {
          // determine versions
          api.forEach(function (versionEntry) {
            if (
              groupEntry === versionEntry.group &&
              entry.name === versionEntry.name
            ) {
              if (!articleVersions[entry.group].hasOwnProperty(entry.name)) {
                articleVersions[entry.group][entry.name] = [];
              }
              articleVersions[entry.group][entry.name].push(
                versionEntry.version
              );
            }
          });
          fields = {
            article: entry,
            versions: articleVersions[entry.group][entry.name],
          };
        } else {
          fields = {
            article: entry,
            hidden: true,
            versions: articleVersions[entry.group][entry.name],
          };
        }

        if (apiProject.sampleUrl == false) {
          fields.article.sampleRequest = [
            {
              url: baseURL + fields.article.url,
            },
          ];
        }

        // add prefix URL for endpoint unless it's already absolute
        if (apiProject.url) {
          if (fields.article.url.substr(0, 4).toLowerCase() !== "http") {
            fields.article.url = apiProject.url + fields.article.url;
          }
        }

        addArticleSettings(fields, entry);

        if (entry.groupTitle) title = entry.groupTitle;

        // TODO: make groupDescription compareable with older versions (not important for the moment)
        if (entry.groupDescription) description = entry.groupDescription;

        articles.push({
          article: templateArticle(fields),
          group: entry.group,
          name: entry.name,
          aloneDisplay: apiProject.template.aloneDisplay,
        });
        oldName = entry.name;
      }
    });

    // render Section with Articles
    var fields = {
      group: groupEntry,
      title: title,
      description: description,
      articles: articles,
      aloneDisplay: apiProject.template.aloneDisplay,
    };
    content += templateSections(fields);
  });
  $("#sections").append(content);

  // Bootstrap Scrollspy
  $(this).scrollspy({ target: "#scrollingNav" });

  // Content-Scroll on Navigation click.
  $(".sidenav")
    .find("a")
    .on("click", function (e) {
      e.preventDefault();
      var id = $(this).attr("href");
      if ($(id).length > 0)
        $("html,body").animate(
          { scrollTop: parseInt($(id).offset().top) },
          400
        );
      window.location.hash = $(this).attr("href");
    });

  /**
   * Check if Parameter (sub) List has a type Field.
   * Example: @apiSuccess          varname1 No type.
   *          @apiSuccess {String} varname2 With type.
   *
   * @param {Object} fields
   */
  function _hasTypeInFields(fields) {
    var result = false;
    $.each(fields, function (name) {
      result =
        result ||
        _.some(fields[name], function (item) {
          return item.type;
        });
    });
    return result;
  }

  /**
   * On Template changes, recall plugins.
   */
  function initDynamic() {
    // Bootstrap popover
    $('button[data-toggle="popover"]')
      .popover()
      .click(function (e) {
        e.preventDefault();
      });

    var version = $("#version strong").html();
    $("#sidenav li").removeClass("is-new");
    if (apiProject.template.withCompare) {
      $("#sidenav li[data-version='" + version + "']").each(function () {
        var group = $(this).data("group");
        var name = $(this).data("name");
        var length = $(
          "#sidenav li[data-group='" + group + "'][data-name='" + name + "']"
        ).length;
        var index = $(
          "#sidenav li[data-group='" + group + "'][data-name='" + name + "']"
        ).index($(this));
        if (length === 1 || index === length - 1) $(this).addClass("is-new");
      });
    }

    // tabs
    $(".nav-tabs-examples a").click(function (e) {
      e.preventDefault();
      $(this).tab("show");
    });
    $(".nav-tabs-examples").find("a:first").tab("show");

    // sample header-content-type switch
    $(".sample-header-content-type-switch").change(function () {
      var paramName = "." + $(this).attr("name") + "-fields";
      var bodyName = "." + $(this).attr("name") + "-body";
      var selectName = "select[name=" + $(this).attr("name") + "]";
      if ($(this).val() == "body-json") {
        $(selectName).val("undefined");
        $(this).val("body-json");
        $(paramName).removeClass("hide");
        $(this).parent().nextAll(paramName).first().addClass("hide");
        $(bodyName).addClass("hide");
        $(this).parent().nextAll(bodyName).first().removeClass("hide");
      } else if ($(this).val() == "body-form-data") {
        $(selectName).val("undefined");
        $(this).val("body-form-data");
        $(bodyName).addClass("hide");
        $(paramName).removeClass("hide");
      } else {
        $(this).parent().nextAll(paramName).first().removeClass("hide");
        $(this).parent().nextAll(bodyName).first().addClass("hide");
      }
      $(this).prev(".sample-request-switch").prop("checked", true);
    });

    // sample request switch
    $(".sample-request-switch").click(function (e) {
      var paramName = "." + $(this).attr("name") + "-fields";
      var bodyName = "." + $(this).attr("name") + "-body";
      var select = $(this)
        .next("." + $(this).attr("name") + "-select")
        .val();
      if ($(this).prop("checked")) {
        if (select == "body-json") {
          $(this).parent().nextAll(bodyName).first().removeClass("hide");
        } else {
          $(this).parent().nextAll(paramName).first().removeClass("hide");
        }
      } else {
        if (select == "body-json") {
          $(this).parent().nextAll(bodyName).first().addClass("hide");
        } else {
          $(this).parent().nextAll(paramName).first().addClass("hide");
        }
      }
    });

    if (apiProject.template.aloneDisplay) {
      //show group
      $(".show-group").click(function () {
        var apiGroup = "." + $(this).attr("data-group") + "-group";
        var apiGroupArticle = "." + $(this).attr("data-group") + "-article";
        $(".show-api-group").addClass("hide");
        $(apiGroup).removeClass("hide");
        $(".show-api-article").addClass("hide");
        $(apiGroupArticle).removeClass("hide");
      });

      //show api
      $(".show-api").click(function () {
        var apiName = "." + $(this).attr("data-name") + "-article";
        var apiGroup = "." + $(this).attr("data-group") + "-group";
        $(".show-api-group").addClass("hide");
        $(apiGroup).removeClass("hide");
        $(".show-api-article").addClass("hide");
        $(apiName).removeClass("hide");
      });
    }

    // call scrollspy refresh method
    $(window).scrollspy("refresh");

    // init modules
    sampleRequest.initDynamic();
    Prism.highlightAll();
  }
  initDynamic();

  if (apiProject.template.aloneDisplay) {
    var hashVal = window.location.hash;
    if (hashVal != null && hashVal.length !== 0) {
      $("." + hashVal.slice(1) + "-init").click();
    }
  }

  //
  // HTML-Template specific jQuery-Functions
  //
  // Change Main Version
  function setMainVersion(selectedVersion) {
    if (typeof selectedVersion === "undefined") {
      selectedVersion = $("#version strong").html();
    } else {
      $("#version strong").html(selectedVersion);
    }

    // hide all
    $("article").addClass("hide");
    $("#sidenav li:not(.nav-fixed)").addClass("hide");

    // show 1st equal or lower Version of each entry
    $("article[data-version]").each(function (index) {
      var group = $(this).data("group");
      var name = $(this).data("name");
      var version = $(this).data("version");

      if (semver.lte(version, selectedVersion)) {
        if (
          $(
            "article[data-group='" +
              group +
              "'][data-name='" +
              name +
              "']:visible"
          ).length === 0
        ) {
          // enable Article
          $(
            "article[data-group='" +
              group +
              "'][data-name='" +
              name +
              "'][data-version='" +
              version +
              "']"
          ).removeClass("hide");
          // enable Navigation
          $(
            "#sidenav li[data-group='" +
              group +
              "'][data-name='" +
              name +
              "'][data-version='" +
              version +
              "']"
          ).removeClass("hide");
          $("#sidenav li.nav-header[data-group='" + group + "']").removeClass(
            "hide"
          );
        }
      }
    });

    // show 1st equal or lower Version of each entry
    $("article[data-version]").each(function (index) {
      var group = $(this).data("group");
      $("section#api-" + group).removeClass("hide");
      if ($("section#api-" + group + " article:visible").length === 0) {
        $("section#api-" + group).addClass("hide");
      } else {
        $("section#api-" + group).removeClass("hide");
      }
    });

    initDynamic();
    return;
  }
  setMainVersion();

  $("#versions li.version a").on("click", function (e) {
    e.preventDefault();

    setMainVersion($(this).html());
  });

  // compare all article with their predecessor
  $("#compareAllWithPredecessor").on("click", changeAllVersionCompareTo);

  // change version of an article
  $("article .versions li.version a").on("click", changeVersionCompareTo);

  // compare url-parameter
  $.urlParam = function (name) {
    var results = new RegExp("[\\?&amp;]" + name + "=([^&amp;#]*)").exec(
      window.location.href
    );
    return results && results[1] ? results[1] : null;
  };

  if ($.urlParam("compare")) {
    // URL Paramter ?compare=1 is set
    $("#compareAllWithPredecessor").trigger("click");
  }

  // Quick jump on page load to hash position.
  // Should happen after setting the main version
  // and after triggering the click on the compare button,
  // as these actions modify the content
  // and would make it jump to the wrong position or not jump at all.
  if (window.location.hash) {
    var id = decodeURI(window.location.hash);
    if ($(id).length > 0)
      $("html,body").animate({ scrollTop: parseInt($(id).offset().top) }, 0);
  }

  /**
   * Initialize search
   */
  var options = {
    valueNames: ["nav-list-item", "nav-list-url-item"],
  };
  var endpointsList = new List("scrollingNav", options);

  /**
   * Set initial focus to search input
   */
  $("#scrollingNav .sidenav-search input.search").focus();

  /**
   * Detect ESC key to reset search
   */
  $(document).keyup(function (e) {
    if (e.keyCode === 27) $("span.search-reset").click();
  });

  /**
   * Search reset
   */
  $("span.search-reset").on("click", function () {
    $("#scrollingNav .sidenav-search input.search").val("").focus();
    endpointsList.search();
  });

  /**
   * Change version of an article to compare it to an other version.
   */
  function changeVersionCompareTo(e) {
    e.preventDefault();

    var $root = $(this).parents("article");
    var selectedVersion = $(this).html();
    var $button = $root.find(".version");
    var currentVersion = $button.find("strong").html();
    $button.find("strong").html(selectedVersion);

    var group = $root.data("group");
    var name = $root.data("name");
    var version = $root.data("version");

    var compareVersion = $root.data("compare-version");

    if (compareVersion === selectedVersion) return;

    if (!compareVersion && version == selectedVersion) return;

    if (
      (compareVersion && articleVersions[group][name][0] === selectedVersion) ||
      version === selectedVersion
    ) {
      // the version of the entry is set to the highest version (reset)
      resetArticle(group, name, version);
    } else {
      var $compareToArticle = $(
        "article[data-group='" +
          group +
          "'][data-name='" +
          name +
          "'][data-version='" +
          selectedVersion +
          "']"
      );

      var sourceEntry = {};
      var compareEntry = {};
      $.each(apiByGroupAndName[group][name], function (index, entry) {
        if (entry.version === version) sourceEntry = entry;
        if (entry.version === selectedVersion) compareEntry = entry;
      });

      var fields = {
        article: sourceEntry,
        compare: compareEntry,
        versions: articleVersions[group][name],
      };

      // add unique id
      // TODO: replace all group-name-version in template with id.
      fields.article.id =
        fields.article.group +
        "-" +
        fields.article.name +
        "-" +
        fields.article.version;
      fields.article.id = fields.article.id.replace(/\./g, "_");

      fields.compare.id =
        fields.compare.group +
        "-" +
        fields.compare.name +
        "-" +
        fields.compare.version;
      fields.compare.id = fields.compare.id.replace(/\./g, "_");

      var entry = sourceEntry;
      if (entry.parameter && entry.parameter.fields)
        fields._hasTypeInParameterFields = _hasTypeInFields(
          entry.parameter.fields
        );

      if (entry.error && entry.error.fields)
        fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);

      if (entry.success && entry.success.fields)
        fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);

      if (entry.info && entry.info.fields)
        fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);

      var entry = compareEntry;
      if (
        fields._hasTypeInParameterFields !== true &&
        entry.parameter &&
        entry.parameter.fields
      )
        fields._hasTypeInParameterFields = _hasTypeInFields(
          entry.parameter.fields
        );

      if (
        fields._hasTypeInErrorFields !== true &&
        entry.error &&
        entry.error.fields
      )
        fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);

      if (
        fields._hasTypeInSuccessFields !== true &&
        entry.success &&
        entry.success.fields
      )
        fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);

      if (
        fields._hasTypeInInfoFields !== true &&
        entry.info &&
        entry.info.fields
      )
        fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);

      var content = templateCompareArticle(fields);
      $root.after(content);
      var $content = $root.next();

      // Event on.click re-assign
      $content
        .find(".versions li.version a")
        .on("click", changeVersionCompareTo);

      // select navigation
      $(
        "#sidenav li[data-group='" +
          group +
          "'][data-name='" +
          name +
          "'][data-version='" +
          currentVersion +
          "']"
      ).addClass("has-modifications");

      $root.remove();
      // TODO: on change main version or select the highest version re-render
    }

    initDynamic();
  }

  /**
   * Compare all currently selected Versions with their predecessor.
   */
  function changeAllVersionCompareTo(e) {
    e.preventDefault();
    $("article:visible .versions").each(function () {
      var $root = $(this).parents("article");
      var currentVersion = $root.data("version");
      var $foundElement = null;
      $(this)
        .find("li.version a")
        .each(function () {
          var selectVersion = $(this).html();
          if (selectVersion < currentVersion && !$foundElement)
            $foundElement = $(this);
        });

      if ($foundElement) $foundElement.trigger("click");
    });
    initDynamic();
  }

  /**
   * Sort the fields.
   */
  function sortFields(fields_object) {
    $.each(fields_object, function (key, fields) {
      var reversed = fields.slice().reverse();

      var max_dot_count = Math.max.apply(
        null,
        reversed.map(function (item) {
          return item.field.split(".").length - 1;
        })
      );

      for (var dot_count = 1; dot_count <= max_dot_count; dot_count++) {
        reversed.forEach(function (item, index) {
          var parts = item.field.split(".");
          if (parts.length - 1 == dot_count) {
            var fields_names = fields.map(function (item) {
              return item.field;
            });
            if (parts.slice(1).length >= 1) {
              var prefix = parts.slice(0, parts.length - 1).join(".");
              var prefix_index = fields_names.indexOf(prefix);
              if (prefix_index > -1) {
                fields.splice(fields_names.indexOf(item.field), 1);
                fields.splice(prefix_index + 1, 0, item);
              }
            }
          }
        });
      }
    });
  }

  /**
   * Add article settings.
   */
  function addArticleSettings(fields, entry) {
    // add unique id
    // TODO: replace all group-name-version in template with id.
    fields.id =
      fields.article.group +
      "-" +
      fields.article.name +
      "-" +
      fields.article.version;
    fields.id = fields.id.replace(/\./g, "_");

    if (entry.header && entry.header.fields) {
      sortFields(entry.header.fields);
      fields._hasTypeInHeaderFields = _hasTypeInFields(entry.header.fields);
    }

    if (entry.parameter && entry.parameter.fields) {
      sortFields(entry.parameter.fields);
      fields._hasTypeInParameterFields = _hasTypeInFields(
        entry.parameter.fields
      );
    }

    if (entry.error && entry.error.fields) {
      sortFields(entry.error.fields);
      fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
    }

    if (entry.success && entry.success.fields) {
      sortFields(entry.success.fields);
      fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
    }

    if (entry.info && entry.info.fields) {
      sortFields(entry.info.fields);
      fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
    }

    // add template settings
    fields.template = apiProject.template;
  }

  /**
   * Render Article.
   */
  function renderArticle(group, name, version) {
    var entry = {};
    $.each(apiByGroupAndName[group][name], function (index, currentEntry) {
      if (currentEntry.version === version) entry = currentEntry;
    });
    var fields = {
      article: entry,
      versions: articleVersions[group][name],
    };

    addArticleSettings(fields, entry);

    return templateArticle(fields);
  }

  /**
   * Render original Article and remove the current visible Article.
   */
  function resetArticle(group, name, version) {
    var $root = $(
      "article[data-group='" + group + "'][data-name='" + name + "']:visible"
    );
    var content = renderArticle(group, name, version);

    $root.after(content);
    var $content = $root.next();

    // Event on.click needs to be reassigned (should actually work with on ... automatically)
    $content.find(".versions li.version a").on("click", changeVersionCompareTo);

    $(
      "#sidenav li[data-group='" +
        group +
        "'][data-name='" +
        name +
        "'][data-version='" +
        version +
        "']"
    ).removeClass("has-modifications");

    $root.remove();
    return;
  }

  /**
   * Return ordered entries by custom order and append not defined entries to the end.
   * @param  {String[]} elements
   * @param  {String[]} order
   * @param  {String}   splitBy
   * @return {String[]} Custom ordered list.
   */
  function sortByOrder(elements, order, splitBy) {
    var results = [];
    order.forEach(function (name) {
      if (splitBy)
        elements.forEach(function (element) {
          var parts = element.split(splitBy);
          var key = parts[0]; // reference keep for sorting
          if (key == name || parts[1] == name) results.push(element);
        });
      else
        elements.forEach(function (key) {
          if (key == name) results.push(name);
        });
    });
    // Append all other entries that ar not defined in order
    elements.forEach(function (element) {
      if (results.indexOf(element) === -1) results.push(element);
    });
    return results;
  }
  Prism.highlightAll();
}
